package mt.filter;

import java.util.PriorityQueue;

import mt.Order;
import mt.comm.ServerComm;
import mt.comm.ServerSideMessage;
import mt.filter.clock.Clock;
import mt.filter.clock.ClockAware;
import mt.filter.clock.SystemClock;
import mt.filter.messagewrapper.DisconnectServerSideMessage;
import mt.filter.messagewrapper.TimeStampedServerSideMessage;
import mt.filter.user.TimeStampedUser;

public class AnalyticsFilter implements ServerComm, ClockAware {

	private static final int BLACKLIST_MAX_MSG_THRESHOLD = 3;
	private static final int BLACKLIST_TIME = 30;
	private static final int LATEST_MESSAGES_TIME = 10;

	private ServerComm decoratedServerComm;
	private Clock clockManager;
	private double orderMessageCounter = 0;
	private PriorityQueue<TimeStampedUser> blackListedUsers = new PriorityQueue<TimeStampedUser>(
			100);
	// messages from the last 10 seconds
	private PriorityQueue<TimeStampedServerSideMessage> latestMessages = new PriorityQueue<TimeStampedServerSideMessage>();

	/**
	 * Test Constructor should only be used during unit testing
	 * 
	 * @param serverCommToDecorate
	 *            ServerComm instance to decorate
	 * @param customClock
	 *            customClockManager with Dummy timer
	 */
	public AnalyticsFilter(ServerComm serverCommToDecorate, Clock customClock) {
		this.decoratedServerComm = serverCommToDecorate;
		this.clockManager = customClock;
		this.clockManager.addClockAware(this);
	}

	/**
	 * Default constructor for standard usage
	 * 
	 * @param serverCommToDecorate
	 *            ServerComm instance to decorate
	 */
	public AnalyticsFilter(ServerComm serverCommToDecorate) {
		this.decoratedServerComm = serverCommToDecorate;
		this.clockManager = new SystemClock();
		this.clockManager.addClockAware(this);
	}

	@Override
	public void start() {
		decoratedServerComm.start();
		this.clockManager.start();
	}

	@Override
	public ServerSideMessage getNextMessage() {
		ServerSideMessage message = decoratedServerComm.getNextMessage();
		latestMessages.add(new TimeStampedServerSideMessage(message,
				this.clockManager.getTime()));

		if (message.getType() == ServerSideMessage.Type.NEW_ORDER) {
			orderMessageCounter++;
		}

		message = verifyOnBlackList(message);

		return message;
	}

	@Override
	public boolean hasNextMessage() {
		return decoratedServerComm.hasNextMessage();
	}

	@Override
	public void sendOrder(String receiversNickname, Order order) {
		decoratedServerComm.sendOrder(receiversNickname, order);
	}

	@Override
	public void sendError(String toNickname, String error) {
		decoratedServerComm.sendError(toNickname, error);
	}

	@Override
	public boolean clientIsConnected(String nickname) {
		return decoratedServerComm.clientIsConnected(nickname);
	}

	@Override
	public void disconnectClient(String nickname) {
		decoratedServerComm.disconnectClient(nickname);
	}

	@Override
	public void tick() {
		printStatisticsMessage();
		updateBlackList();
		updateLatestMessages();
	}

	/**
	 * Runs every tick of the clock and prints the statistics every 60 ticks
	 */
	private void printStatisticsMessage() {
		if ((int) clockManager.getTime() % 60 == 0) {
			System.out.println("Average orders received per minute: " + orderMessageCounter);
			orderMessageCounter = 0;
		}
	}

	/**
	 * Runs every tick of the clock, removes from the blacklist users older than
	 * {@link AnalyticsFilter#BLACKLIST_TIME}
	 */
	public void updateBlackList() {
		double currentTime = this.clockManager.getTime();
		while (this.blackListedUsers.size() > 0
				&& currentTime - this.blackListedUsers.peek().getTimeStamp() > BLACKLIST_TIME) {
			this.blackListedUsers.poll();
			System.out.println(currentTime);
		}
	}

	/**
	 * Runs every tick of the clock, removes messages older than
	 * {@link AnalyticsFilter#LATEST_MESSAGES_TIME} ticks from the queue
	 */
	public void updateLatestMessages() {
		double currentTime = this.clockManager.getTime();
		while (this.latestMessages.size() > 0
				&& currentTime - this.latestMessages.peek().getTimeStamp() > LATEST_MESSAGES_TIME) {
			this.latestMessages.poll();
		}
	}

	/**
	 * Runs on every message received.
	 * 
	 * Verifies if the user has sent more than
	 * {@link AnalyticsFilter#BLACKLIST_MAX_MSG_THRESHOLD} messages. If so adds
	 * the user to the blacklist.
	 * 
	 * Verifies if the user is blacklisted If so calls disconnectClient and
	 * returns a DisconnectMessage
	 * 
	 * @param message
	 *            Received ServerSideMessage
	 * @return the new message to be passed on
	 */
	private ServerSideMessage verifyOnBlackList(ServerSideMessage message) {
		TimeStampedUser timeStampedUser = new TimeStampedUser(
				message.getSenderNickname(), this.clockManager.getTime());

		// should be blacklisted?
		if (countUserMessages(message.getSenderNickname()) > BLACKLIST_MAX_MSG_THRESHOLD) {
			this.blackListedUsers.add(timeStampedUser);
		}

		// is black listed
		if (this.blackListedUsers.contains(timeStampedUser)) {
			disconnectClient(message.getSenderNickname());
			return new DisconnectServerSideMessage(message);
		}

		return message;
	}

	/**
	 * Counts how many ServerSideMessages the user has in the lastestMessages
	 * queue
	 * 
	 * @param user
	 *            User's username
	 * @return number of messages
	 */
	private int countUserMessages(String user) {
		int rtn = 0;

		for (ServerSideMessage message : latestMessages) {
			if (message.getSenderNickname().equals(user)) {
				rtn++;
			}
		}

		return rtn;
	}
}
